package net.wangds.program.comp.lock;

import net.wangds.log.helper.LogHelper;
import net.wangds.redis.comp.RedisLock;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Objects;
import java.util.function.Supplier;

/**
 * redis锁.
 * <p>
 * 跨线程使用的时候会被识别成两个锁.
 * </p>
 *
 * @author 王东石 2020/7/4 9:55.
 */
@Component
public class LockComp {


    @Autowired
    private RedisLock mmLock;

    /**
     * 本地线程：账户锁定.
     */
    private ThreadLocal<String> thlcAccountBalance = new ThreadLocal<>();

    /**
     * 锁定账户后执行.
     *
     * @param uid  用户.
     * @param task 任务.
     */
    public void lockAccountBalance(String uid, Runnable task) {
        lock(thlcAccountBalance, () -> "lock-balance-" + uid, task, 100, 30);
    }

    /**
     * 通过ThreadLocal支持多级锁定.
     *
     * @param thlc          ThreadLocal对象.
     * @param keyBuilder    锁定键.
     * @param task          要执行的任务.
     * @param sleepMs       等待其他资源执行时间.
     * @param maxSleepCount 等待其他资源次数上限.
     */
    private void lock(final ThreadLocal<String> thlc, final Supplier<String> keyBuilder, final Runnable task,
                      final int sleepMs, final int maxSleepCount) {
        Objects.requireNonNull(thlc);
        Objects.requireNonNull(keyBuilder);

        String lockid = keyBuilder.get();

        String localKey = thlc.get();
        int sleepCount = 0;
        while (true) {
            //LogHelper.dev("本地锁检查!"+lockid);
            if (StringUtils.isNotBlank(localKey) && !StringUtils.equals(lockid, localKey)) {
                //已经有锁定，但是本地线程锁
                try {
                    Thread.sleep(sleepMs);
                    sleepCount++;
                } catch (InterruptedException e) {
                    LogHelper.warn(e.getLocalizedMessage(), e);
                }
            } else {
                break;
            }

            if (sleepCount > maxSleepCount) { //至少等待3秒
                throw new RuntimeException("无法获得锁:" + lockid);
            }

            if (StringUtils.isBlank(thlc.get())) {
                synchronized (thlc) {
                    if (StringUtils.isBlank(thlc.get())) { //双检查
                        thlc.set(lockid);
                    } else {
                        continue;
                    }
                }
            } else {
                continue;
            }
            break;
        }


        localKey = thlc.get();
        if (StringUtils.isNotBlank(localKey) && StringUtils.equals(lockid, localKey)) {
           //LogHelper.dev("共享本地锁:" + lockid);
            task.run();
        } else {
            //LogHelper.dev("获得新的redis锁:" + lockid);
            mmLock.whenLock(lockid,

                    () -> {
                        thlc.set(lockid);
                        try {
                            if (task != null) {
                                task.run();
                            }
                        } catch (Exception ex) {
                            LogHelper.warn(ex);
                        } finally {
                            thlc.remove();
                        }
                    }

                    , 100, 100, 1);
        }

    }

}
